package cn.juque.lucenecandy.core.base;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.NumberConstant;
import cn.juque.common.constants.YesOrNoEnum;
import cn.juque.common.exception.AppException;
import cn.juque.lucenecandy.bo.FieldBO;
import cn.juque.lucenecandy.bo.IndexBO;
import cn.juque.lucenecandy.cache.IndexInfoCache;
import cn.juque.lucenecandy.core.constants.StrConstant;
import cn.juque.lucenecandy.core.enums.MatchTypeEnum;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.springframework.util.ReflectionUtils;

import javax.validation.constraints.NotNull;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>Builder</li>
 * </ul>
 * @date 2023-12-26
 **/
@Slf4j
@Data
@ToString
public abstract class AbstractBuilder<T extends BaseEntity, M extends AbstractBuilder<?, ?>> {

    private static final String GET = "get";

    private static final String IS = "is";

    protected Class<T> indexClass;

    protected IndexBO indexInfo;

    protected Map<Query, BooleanClause.Occur> queryMap;

    protected Set<String> fieldToLoad;
    protected M thisType;

    protected List<SortField> sortFieldList;

    protected AbstractBuilder() {
    }

    AbstractBuilder(Class<T> indexClass) {
        this.indexClass = indexClass;
        this.fieldToLoad = new HashSet<>();
        this.queryMap = new HashMap<>();
        this.sortFieldList = CollUtil.newArrayList();
        this.loadIndexInfo();
        this.queryMap.put(
                IntPoint.newRangeQuery(
                        StrConstant.D_VERSION, NumberConstant.ZERO, Integer.MAX_VALUE), BooleanClause.Occur.MUST);
        this.queryMap.put(
                IntPoint.newExactQuery(
                        StrConstant.D_VISIBLE, YesOrNoEnum.YES.getCode()), BooleanClause.Occur.MUST);
    }

    /**
     * 构造查询器
     *
     * @return AbstractWrapper
     */
    abstract AbstractWrapper build();

    /**
     * 字符串相关匹配
     *
     * @param sfunction get方法
     * @param value     值
     * @param matchType 匹配方式
     * @param occur     匹配逻辑
     */
    public M matchStr(Sfunction<T, ?> sfunction, String value, MatchTypeEnum matchType, BooleanClause.Occur occur) {
        Query query;
        switch (matchType) {
            case LEFT_LIKE:
                query = new WildcardQuery(new Term(this.field(sfunction), "*" + value));
                this.queryMap.put(query, occur);
                break;
            case RIGHT_LIKE:
                query = new WildcardQuery(new Term(this.field(sfunction), value + "*"));
                this.queryMap.put(query, occur);
                break;
            case ALL_LIKE:
                query = new WildcardQuery(new Term(this.field(sfunction), "*" + value + "*"));
                this.queryMap.put(query, occur);
                break;
            case PREFIX:
                query = new PrefixQuery(new Term(this.field(sfunction), value));
                this.queryMap.put(query, occur);
                break;
            case FUZZY:
                query = new FuzzyQuery(new Term(this.field(sfunction), value));
                this.queryMap.put(query, occur);
                break;
            default:
                query = new TermQuery(new Term(this.field(sfunction), value));
                this.queryMap.put(query, occur);
                break;
        }
        return this.thisType;
    }

    public M matchInt(Sfunction<T, ?> sfunction, Integer value, BooleanClause.Occur occur) {
        Query query = IntPoint.newExactQuery(this.field(sfunction), value);
        this.queryMap.put(query, occur);
        return this.thisType;
    }

    public M matchLong(Sfunction<T, ?> sfunction, Long value, BooleanClause.Occur occur) {
        Query query = LongPoint.newExactQuery(this.field(sfunction), value);
        this.queryMap.put(query, occur);
        return this.thisType;
    }

    public M matchRange(Sfunction<T, ?> sfunction, Integer min, Integer max, BooleanClause.Occur occur) {
        Query query = IntPoint.newRangeQuery(this.field(sfunction), min, max);
        this.queryMap.put(query, occur);
        return this.thisType;
    }

    public M matchRange(Sfunction<T, ?> sfunction, Long min, Long max, BooleanClause.Occur occur) {
        Query query = LongPoint.newRangeQuery(this.field(sfunction), min, max);
        this.queryMap.put(query, occur);
        return this.thisType;
    }

    public M matchRange(Sfunction<T, ?> sfunction, Date begin, Date end, BooleanClause.Occur occur) {
        if (Objects.nonNull(begin)) {
            Query query = LongPoint.newRangeQuery(this.field(sfunction), begin.getTime(), Integer.MAX_VALUE);
            this.queryMap.put(query, occur);
        }
        if (Objects.nonNull(end)) {
            Query query = LongPoint.newRangeQuery(this.field(sfunction), Integer.MIN_VALUE, end.getTime());
            this.queryMap.put(query, occur);
        }
        return this.thisType;
    }

    public M matchBool(@NotNull Query query, @NotNull BooleanClause.Occur occur) {
        this.queryMap.put(query, occur);
        return this.thisType;
    }

    /**
     * 添加排序规则
     *
     * @param sfunction 字段
     * @param type      类型
     * @param reverse   是否倒序
     * @return M
     */
    public M addSort(Sfunction<T, ?> sfunction, SortField.Type type, Boolean reverse) {
        SortField sortField = new SortField(this.field(sfunction), type, Boolean.TRUE.equals(reverse));
        this.sortFieldList.add(sortField);
        return this.thisType;
    }

    @SafeVarargs
    public final void addField(Sfunction<T, ?>... sfunctions) {
        for (Sfunction<T, ?> sfunction : sfunctions) {
            this.fieldToLoad.add(this.field(sfunction));
        }
    }

    protected String field(Sfunction<T, ?> function) {
        Method method;
        try {
            method = function.getClass().getDeclaredMethod("writeReplace");
            if (!method.isAccessible()) {
                ReflectionUtils.makeAccessible(method);
            }
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String getter = serializedLambda.getImplMethodName();
            String fieldName = null;
            if (CharSequenceUtil.startWith(getter, GET)) {
                fieldName = CharSequenceUtil.subAfter(getter, GET, false);
            }
            if (CharSequenceUtil.startWith(getter, IS)) {
                fieldName = CharSequenceUtil.subAfter(getter, IS, false);
            }
            if (CharSequenceUtil.isEmpty(fieldName)) {
                throw new AppException(MessageEnum.SYSTEM_ERROR.getCode(), "[" + getter + "]方法名定义不规范");
            }
            Map<String, FieldBO> fieldMap = this.indexInfo.getFieldMap();
            FieldBO bo = fieldMap.get(CharSequenceUtil.lowerFirst(fieldName));
            return bo.getFieldName();
        } catch (Exception e) {
            log.error("writeReplace error", e);
            throw new AppException(MessageEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 加载索引
     */
    private void loadIndexInfo() {
        IndexInfoCache indexInfoCache = SpringUtil.getBean(IndexInfoCache.BEAN_ID);
        IndexBO indexBO = indexInfoCache.get(ClassUtil.getClassName(this.indexClass, false), false);
        this.indexInfo = indexBO;
        if (Objects.isNull(indexBO)) {
            indexInfoCache.refresh();
        }
        this.indexInfo = indexInfoCache.get(ClassUtil.getClassName(this.indexClass, false), false);
    }
}
